package com.amos.stuff.filter.handler;

import com.alibaba.fastjson.JSONObject;
import com.amos.stuff.filter.anno.AmMapping;
import com.amos.stuff.filter.bean.BaseDTO;
import com.amos.stuff.filter.bean.FilterRule;
import com.amos.stuff.filter.bean.MockNode;
import com.amos.stuff.util.ClassUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Copyright © 2018 五月工作室. All rights reserved.
 *
 * @Project: stuff
 * @ClassName: DataFilterHandler
 * @Package: com.amos.stuff.filter.handler
 * @author: zhuqb
 * @Description: 数据过滤处理器
 * @date: 2019/10/9 0009 下午 17:26
 * @Version: V1.0
 */
public class DataFilterHandler<T extends BaseDTO> {

    public final Logger logger = LoggerFactory.getLogger(this.getClass());
    /**
     * 属性全路径默认分隔符
     */
    public static final String DEFAULT_SPLIT_STR = ".";

    public static final int DEFAULT_MAP_LENGTH = 100;
    /**
     * 泛型为 BaseDTO 的实体类对象
     */
    private T dto;
    /**
     * 实体类对象的 Class
     */
    private Class clazz;

    /**
     * DTO中与Entity中字段的映射关系
     * Key 是DTO中的属性全称
     * Value 是Entity中与DTO匹配上的属性的全称
     * <p>
     * 该映射包括子集的属性列表
     */
    private Map<String, String> mapping = new ConcurrentHashMap<>(DEFAULT_MAP_LENGTH);

    /**
     * 需要过滤的字段列表
     */
    private List<FilterRule> filterList;

    /**
     * 模型结构
     */
    private MockNode node = null;

    /**
     * 构造器初始化
     *
     * @param dto
     * @param filterList
     */
    public DataFilterHandler(T dto, List<FilterRule> filterList) {
        this.dto = dto;
        this.clazz = dto.getClass();
        this.filterList = filterList;
        // 获取DTO与Entity之间的映射关系
        this.mapping();
        this.node = this.initMockNode(null, this.clazz);
        this.logger.info("组装之后的Node节点数据：{}", JSONObject.toJSONString(this.node));
    }

    /**
     * 初始化节点数据
     * <p>
     * 该方法主要是构建实体对象中的Node节点数据
     * 将实体类中含有需要过滤的属性集数据解析组装成固有数据结构的Node节点
     *
     * @param pre 上一个节点的对象
     * @param cls 当前节点实体Class
     * @return
     */
    private MockNode initMockNode(MockNode pre, Class<?> cls) {
        MockNode node = new MockNode();
        // 前一个节点 对于root节点来说，前一个节点为空
        node.setPre(pre);
        // 本节点的class类型 对于属性集来说，class类型是属性集的泛型
        // 由于这里通过 注解 @RxPrintBean 来判断是否需要过滤的，需要属性集中的泛型对应的DTO对象也需要添加相应的注解
        node.setClazz(cls);
        // 获取本节点对象的所有的Field
        List<Field> fields = FieldUtils.getAllFieldsList(cls);

        List<MockNode> nexts = new ArrayList<>();
        List<String> noShowFields = new ArrayList<>();
        for (Field field : fields) {
            field.setAccessible(Boolean.TRUE);
            if (field.getType().isAssignableFrom(List.class) && field.isAnnotationPresent(AmMapping.class)) {
                this.logger.info("属性集Node:{}", field.getName());
                try {
                    Class<?> lClazz = ClassUtils.getFieldType(field);
                    nexts.add(this.initMockNode(node, lClazz));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                // 属性全称
                String fieldName = cls.getName() + DEFAULT_SPLIT_STR + field.getName();
                // 如果 过滤列表中属性全称和 映射关系中的 Entity 属性全称相同
                // 并且 od是否发送为不发送
                for (FilterRule od : this.filterList) {
                    for (Map.Entry<String, String> entry : this.mapping.entrySet()) {
                        if (entry.getValue().equals(od.getEntityFieldFullName())
                                && !od.getShow()
                                && fieldName.equals(entry.getKey())) {
                            noShowFields.add(fieldName);
                        }
                    }
                }
            }
        }
        node.setNexts(nexts);
        node.setNoShowFields(noShowFields);
        return node;
    }

    /**
     * 获取映射关系
     */
    private void mapping() {
        try {
            this.logger.info("开始组装DTO与Entity之间的映射关系");
            this.iteratorClass(this.clazz);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 循环迭代生成DTO对象与Entity对象属性对应的映射关系
     *
     * @param dtoClazz DTO 对象class
     */
    public void iteratorClass(Class dtoClazz) {
        /**
         * 如果实体类中没有 RxPrintBean的注解 则直接返回
         */
        if (!this.clazz.isAnnotationPresent(AmMapping.class)) {
            return;
        }
        // 获取类上的 RxPrintBean 的注解
        AmMapping rpb = AnnotationUtils.findAnnotation(dtoClazz, AmMapping.class);
        if (StringUtils.isEmpty(rpb)) {
            throw new RuntimeException(dtoClazz.getName() + "需要添加注解:@AmMapping");
        }
        Class cls = rpb.entity();
        // 获取注解值 指定类的 Field 属性列表
        List<Field> srcList = FieldUtils.getAllFieldsList(cls);

        // 获取目标的属性列表
        List<Field> destList = FieldUtils.getAllFieldsList(dtoClazz);
        for (Field field : destList) {
            Boolean accessible = field.isAccessible();
            field.setAccessible(Boolean.TRUE);
            // 循环生成属性列表
            if (field.getType().isAssignableFrom(List.class)) {
                if (field.isAnnotationPresent(AmMapping.class)) {
                    // 属性集的泛型
                    Class<?> lClazz = ClassUtils.getFieldType(field);
                    if (!StringUtils.isEmpty(lClazz)) {
                        this.logger.info("实体中的集合：{}", lClazz);
                        this.iteratorClass(lClazz);
                    }
                }
            } else {

                // 生成DTO与Entity之间的映射关系
                for (Field srcField : srcList) {
                    if (srcField.getName().equals(field.getName())) {
                        this.mapping.put(dtoClazz.getName() + DEFAULT_SPLIT_STR + field.getName(), cls.getName() + DEFAULT_SPLIT_STR + srcField.getName());
                        continue;
                    }
                }

            }
            field.setAccessible(accessible);
        }
    }

    /**
     * 过滤数据项的方法
     * 1. 先过滤父类的数据，然后迭代过滤属性集中的数据
     *
     * @return
     */
    public T filter() {
        // 如果没有过滤的字段 ，则全部返回
        if (CollectionUtils.isEmpty(this.filterList)) {
            return this.dto;
        }
        this.logger.info("过滤之前的数据:{}", JSONObject.toJSONString(this.dto));
        try {
            this.filterData(this.dto, this.node.getNoShowFields());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        } finally {
            this.mapping.clear();
        }
        this.logger.info("过滤之后的数据:{}", JSONObject.toJSONString(this.dto));
        return this.dto;
    }

    /**
     * 过滤数据，主要用来过滤父级的数据，在父级属性过滤的过程中，过滤属性集中的数据
     *
     * @param dto              DTO 对象
     * @param nowShowFieldList 不需要展示的DTO属性列表
     * @throws Exception
     */
    private void filterData(T dto, List<String> nowShowFieldList) throws Exception {
        try {
            Class<?> dtoClass = dto.getClass();

            List<Field> fields = FieldUtils.getAllFieldsList(dtoClass);
            String fieldName = null;
            for (Field field : fields) {
                field.setAccessible(Boolean.TRUE);
                fieldName = dtoClass.getName() + DEFAULT_SPLIT_STR + field.getName();
                if (nowShowFieldList.contains(fieldName)) {

                    field.set(dto, null);
                }
                if (field.getType().isAssignableFrom(List.class) && field.isAnnotationPresent(AmMapping.class)) {
                    List<T> list = (List<T>) field.get(dto);
                    this.filterData(this.node, list);
                    field.set(dto, list);
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 迭代循环过滤数据，主要是用来过滤属性集中的数据
     *
     * @param umn  Node节点数据
     * @param list 属性集数据
     * @throws Exception
     */
    private void filterData(MockNode umn, List<T> list) throws Exception {
        // 获取下级节点，如果没有下级节点就返回
        List<MockNode> nexts = umn.getNexts();
        if (CollectionUtils.isEmpty(nexts)) {
            return;
        }

        Class<?> cls = null;
        // 这里的处理步骤，循环每个属性节点
        // 将每条记录信息里面的所有的属性字段遍历
        // 如果该属性字段是不需要显示值的，则将该属性的值置为空
        // 查看本级属性有没有是属性集的，如果有的话，则循环迭代继续重复上述的步骤来过滤数据
        for (MockNode mockNode : nexts) {
            cls = mockNode.getClazz();
            this.logger.info("当前节点的Class信息:{}", cls);
            // 获取本级不需要显示的字段信息
            List<String> noShowFields = mockNode.getNoShowFields();
            // 获取本级所有的属性字段信息
            List<Field> fields = FieldUtils.getAllFieldsList(cls);
            String fieldName = null;
            for (T t : list) {
                for (Field field : fields) {
                    fieldName = cls.getName() + DEFAULT_SPLIT_STR + field.getName();
                    if (noShowFields.contains(fieldName)) {
                        field.setAccessible(Boolean.TRUE);

                        field.set(t, null);
                    }
                    if (field.getType().isAssignableFrom(List.class) && field.isAnnotationPresent(AmMapping.class)) {
                        List<T> data = (List<T>) field.get(t);
                        this.filterData(mockNode, data);
                        // 过滤后的数据需要重新赋值到原来的属性中
                        field.set(t, data);
                    }
                }
            }
        }
    }
}
